package se.lnu.axez.physics.bullet;

import se.lnu.axez.FloatTriple;
import android.util.Log;

public class BulletFacade {
	private final static String TAG = BulletFacade.class.getName();
	private final static BulletFacade instance = new BulletFacade();
	private ISimulationCallback callback;

	static {
		System.loadLibrary("bullet");
	}

	public static BulletFacade getInstance() {
		return instance;
	}
	
	private BulletFacade() {
		
	}
	
	public int createDiscreteDynamicsWorld(FloatTriple gravity){
		return this.createDiscreteDynamicsWorldNative(gravity);
	}
	
	public void doSimulation(ISimulationCallback updateListener, 
							 int worldID, float timeStep, int maxSubSteps) {
		this.callback = updateListener;
		this.doSimulationNative(worldID, timeStep, maxSubSteps);
		this.callback = null;
	}

	public int pickBody(int worldID, float[] coordinates, int nearOffset, int farOffset) {
		return this.pickBodyNative(worldID, coordinates, nearOffset, farOffset);
	}
	
	public boolean destroyDiscreteDynamicsWorld(int worldID){
		return this.destroyDiscreteDynamicsWorldNative(worldID);
	}
	
	public boolean destroyRigidBody(int worldAddress, int rigidBodyID){
		return this.destroyRigidBodyNative(worldAddress, rigidBodyID);
	}
	
	public boolean destroyConstraint(int worldID, int constraintID) {
		return this.destroyConstraintNative(worldID, constraintID);
	}
	
	public int createBoxCollisionShape(float xSize, float ySize, float zSize) {
		return this.createBoxCollisionShapeNative(xSize, ySize, zSize);
	}
	
	public int createSphereCollisionShape(float radius) {
		return this.createSphereCollisionShapeNative(radius);
	}

	public boolean destroyCollisionShape(int collisionShapeID){
		return this.destroyCollisionShapeNative(collisionShapeID);
	}
	
	public int createRigidBody(
			int worldID, 
			int shapeID,
			float[] orientation,
			float mass,
			int collisionGroup,
			int collisionMask) {
		int bodyId = this.createRigidBodyNative(worldID, shapeID, orientation, mass, collisionGroup, collisionMask, false);
		Log.d(TAG, String.format("Rigid body with address %d created", bodyId));
		return bodyId;
	}
	
	public int createRigidBody(
			int worldID, 
			int shapeID,
			float[] orientation,
			float mass) {
		int bodyId = this.createRigidBodyNative(worldID, shapeID, orientation, mass, 0, 0, true);
		Log.d(TAG, String.format("Rigid body with address %d created", bodyId));
		return bodyId;
	}
	
	public int createPoint2PointConstraint(int worldAddress, int bodyAddress, FloatTriple pivot) {
		return this.createPoint2PointConstraintNative(worldAddress, bodyAddress, pivot);
	}
	
	public int createPoint2PointConstraint(int worldAddress, int body1Address, int body2Address, FloatTriple pivot1, FloatTriple pivot2) {
		return this.createPoint2PointConstraint2BodiesNative(worldAddress, body1Address, body2Address, pivot1, pivot2);
	}
	
	public int createSliderConstraint(int worldAddress, int body1Address, int body2Address, float[] transforms) {
		return this.createSliderConstraint2BodiesNative(
				worldAddress, 
				body1Address, 
				body2Address, 
				transforms);
	}
	
	public int createSliderConstraint(int worldAddress, int bodyAddress, float[] transform, float lowerLinearConstraint, float upperLinearConstraint) {
		return this.createSliderConstraintNative(
				worldAddress, 
				bodyAddress, 
				transform,
				lowerLinearConstraint,
				upperLinearConstraint);
	}
	
	public int createHingeConstraint(int worldAddress, int bodyAddress, FloatTriple pivot, FloatTriple axis) {
		return this.createHingeConstraintNative(worldAddress, bodyAddress, pivot, axis);
	}
	
	public boolean applyCentralImpulse(int bodyID, FloatTriple impulse){
		return this.applyCentralImpulseNative(bodyID, impulse.x(), impulse.y(), impulse.z());
	}
	
	public boolean pushBody(int worldID, int bodyID, FloatTriple direction, float force) {
		return this.pushBodyNative(worldID, bodyID, direction.x(), direction.y(), direction.z(), force);
	}
	
	private void onRigidBodyUpdate(int rigidBodyID, float[] orientationMatrix) {
		if (this.callback != null) {
			this.callback.onRigidBodyUpdate(rigidBodyID, orientationMatrix);
		}
	}
	
	private native boolean pushBodyNative(
			int worldAddress, 
			int bodyAddress, 
			float dirX, 
			float dirY, 
			float dirZ,
			float force);
	private native int pickBodyNative(int worldAddress, float[] coordinates, int nearOffset, int farOffset);
	private native boolean applyCentralImpulseNative(int bodyAddress, float x, float y, float z);
	private native int createSphereCollisionShapeNative(float radius);
	private native int createBoxCollisionShapeNative(float xSize, float ySize, float zSize);
	private native int createDiscreteDynamicsWorldNative(FloatTriple gravity);
	private native int createRigidBodyNative(
			int worldAddress,
			int shapeAddress,
			float[] orientation,
	   		float mass,
	   		int collisionGroup,
	   		int collisionMask,
	   		boolean useDefaultMasks);
	private native boolean destroyDiscreteDynamicsWorldNative(int worldAddress);
	private native boolean destroyRigidBodyNative(int worldAddress, int rigidBodyAddress);
	private native boolean destroyCollisionShapeNative(int collisionShapeAddress);
	private native boolean destroyConstraintNative(int worldAddress, int constraintAddress);
	private native boolean doSimulationNative(
			int worldDataAddress,
			float timeStep, 
			int maxSubSteps);
	private native int createSliderConstraintNative(
			int worldAddress, 
			int bodyAddress, 
			float[] transform,
			float lowerLinearConstraint,
			float upperLinearConstraint);
	private native int createSliderConstraint2BodiesNative(
			int worldAddress, 
			int body1Address, 
			int body2Address, 
			float[] transforms);
	private native int createHingeConstraintNative(
			int worldAddress, 
			int bodyAddress, 
			FloatTriple pivot, 
			FloatTriple axis);
	private native int createPoint2PointConstraintNative(
			int worldAddress, 
			int bodyAddress, 
			FloatTriple pivot);
	private native int createPoint2PointConstraint2BodiesNative(
			int worldAddress, 
			int body1Address, 
			int body2Address, 
			FloatTriple pivot1, 
			FloatTriple pivot2);
}

